
#include "JsonWriter.h"

namespace Veda {

std::ostream& operator<<( std::ostream& sout, const VJson& root )
{
	VJsonStreamWriter writer;
	writer.write(sout, root);
	return sout;
}

VJsonWriter::VJsonWriter()
{
}

VJsonWriter::~VJsonWriter()
{
}

std::string VJsonWriter::write( const VJson& root )
{
	document_ = "";
	writeValue( root );
	document_ += "\n";
	return document_;
}

void VJsonWriter::writeValue( const VJson& value )
{
	switch ( value.type() )
	{
	case nullValue:
		document_ += "null";
		break;
	case intValue:
		document_ += valueToString( value.asInt() );
		break;
	case uintValue:
		document_ += valueToString( value.asUInt() );
		break;
	case realValue:
		document_ += valueToString( value.asDouble() );
		break;
	case stringValue:
		document_ += valueToQuotedString( value.asCString() );
		break;
	case booleanValue:
		document_ += valueToString( value.asBool() );
		break;
	case arrayValue:
		{
			document_ += "[";
			int size = value.size();
			for ( int index =0; index < size; ++index )
			{
				if ( index > 0 )
				document_ += ",";
				writeValue( value[index] );
			}
			document_ += "]";
		}
		break;
	case objectValue:
		{
			VJson::Members members( value.getMemberNames() );
			document_ += "{";
			for ( VJson::Members::iterator it = members.begin(); it != members.end(); ++it )
			{
				const std::string &name = *it;
				if ( it != members.begin() )
				document_ += ",";
				document_ += valueToQuotedString( name.c_str() );
				document_ += ":";
				writeValue( value[name] );
			}
			document_ += "}";
		}
		break;
	}
}

VJsonStyledWriter::VJsonStyledWriter() : rightMargin_( 74 ), indentSize_( 3 )
{
}

VJsonStyledWriter::~VJsonStyledWriter()
{
}

std::string VJsonStyledWriter::write( const VJson& root )
{
	document_ = "";
	addChildValues_ = false;
	indentString_ = "";
	writeValue( root );
	document_ += "\n";
	return document_;
}

void VJsonStyledWriter::writeValue( const VJson& value )
{
	switch ( value.type() )
	{
	case nullValue:
		pushValue( "null" );
		break;
	case intValue:
		pushValue( valueToString( value.asInt() ) );
		break;
	case uintValue:
		pushValue( valueToString( value.asUInt() ) );
		break;
	case realValue:
		pushValue( valueToString( value.asDouble() ) );
		break;
	case stringValue:
		pushValue( valueToQuotedString( value.asCString() ) );
		break;
	case booleanValue:
		pushValue( valueToString( value.asBool() ) );
		break;
	case arrayValue:
		writeArrayValue( value);
		break;
	case objectValue:
		{
			VJson::Members members( value.getMemberNames() );
			if ( members.empty() )
				pushValue( "{}" );
			else
			{
				writeWithIndent( "{" );
				indentString_ += std::string( indentSize_, ' ' );
				VJson::Members::iterator it = members.begin();
				while ( true )
				{
					const std::string& name = *it;
					const VJson &childValue = value[name];
					writeWithIndent( valueToQuotedString( name.c_str() ) );
					document_ += " : ";
					writeValue( childValue );
					if ( ++it == members.end() )
						break;
					document_ += ",";
				}
				if( int(indentString_.size()) < indentSize_ )
				{
					errorf("VJsonStyledWriter in trouble!\n");
					return;
				}
				indentString_.resize( indentString_.size() - indentSize_ );
				writeWithIndent( "}" );
			}
		}
		break;
	}
}

void VJsonStyledWriter::writeArrayValue( const VJson& value )
{
	uint size = value.size();
	if ( size == 0 )
		pushValue( "[]" );
	else
	{
		bool isArrayMultiLine = isMultineArray( value );
		if ( isArrayMultiLine )
		{
			writeWithIndent( "[" );
			indentString_ += std::string( indentSize_, ' ' );
			bool hasChildValue = !childValues_.empty();
			uint index =0;
			while ( true )
			{
				const VJson& childValue = value[index];
				if ( hasChildValue )
					writeWithIndent( childValues_[index] );
				else
				{
					writeIndent();
					writeValue( childValue );
				}
				if ( ++index == size )
					break;
				document_ += ",";
			}
			if( int(indentString_.size()) < indentSize_ )
			{
				errorf("VJsonStyledWriter in trouble!\n");
				return;
			}
			indentString_.resize( indentString_.size() - indentSize_ );
			writeWithIndent( "]" );
		}
		else // output on a single line
		{
			if( childValues_.size() != size )
			{
				errorf("VJsonStyledWriter in trouble!\n");
				return;
			}
			document_ += "[ ";
			for ( uint index =0; index < size; ++index )
			{
				if ( index > 0 )
					document_ += ", ";
				document_ += childValues_[index];
			}
			document_ += " ]";
		}
	}
}

bool VJsonStyledWriter::isMultineArray( const VJson& value )
{
	int size = value.size();
	bool isMultiLine = size*3 >= rightMargin_ ;
	childValues_.clear();
	for ( int index =0; index < size && !isMultiLine; ++index )
	{
		const VJson& childValue = value[index];
		isMultiLine = (isMultiLine || ((childValue.isArray() || childValue.isObject()) && childValue.size() > 0));
	}
	if ( !isMultiLine ) // check if line length > max line length
	{
		childValues_.reserve( size );
		addChildValues_ = true;
		int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
		for ( int index =0; index < size && !isMultiLine; ++index )
		{
			writeValue( value[index] );
			lineLength += int( childValues_[index].length() );
		}
		addChildValues_ = false;
		isMultiLine = isMultiLine || lineLength >= rightMargin_;
	}
	return isMultiLine;
}

void VJsonStyledWriter::pushValue( const std::string& value )
{
   if ( addChildValues_ )
      childValues_.push_back( value );
   else
      document_ += value;
}

void VJsonStyledWriter::writeIndent()
{
	if ( !document_.empty() )
	{
		char last = document_[document_.length()-1];
		if ( last == ' ' )     // already indented
			return;
		if ( last != '\n' )    // Comments may add new-line
			document_ += '\n';
	}
	document_ += indentString_;
}

void VJsonStyledWriter::writeWithIndent( const std::string& value )
{
	writeIndent();
	document_ += value;
}

VJsonStreamWriter::VJsonStreamWriter() : document_(NULL), rightMargin_( 74 ), indentation_( "\t" )
{
}

VJsonStreamWriter::VJsonStreamWriter( std::string indentation ) : document_(NULL), rightMargin_( 74 ), indentation_( indentation )
{
}

VJsonStreamWriter::~VJsonStreamWriter()
{
}

void VJsonStreamWriter::write( std::ostream &out, const VJson& root )
{
	document_ = &out;
	addChildValues_ = false;
	indentString_ = "";
	writeValue( root );
	*document_ << "\n";
	document_ = NULL; // Forget the stream, for safety.
}

void VJsonStreamWriter::writeValue( const VJson& value )
{
	switch ( value.type() )
	{
	case nullValue:
		pushValue( "null" );
		break;
	case intValue:
		pushValue( valueToString( value.asInt() ) );
		break;
	case uintValue:
		pushValue( valueToString( value.asUInt() ) );
		break;
	case realValue:
		pushValue( valueToString( value.asDouble() ) );
		break;
	case stringValue:
		pushValue( valueToQuotedString( value.asCString() ) );
		break;
	case booleanValue:
		pushValue( valueToString( value.asBool() ) );
		break;
	case arrayValue:
		writeArrayValue( value);
		break;
	case objectValue:
		{
			VJson::Members members( value.getMemberNames() );
			if ( members.empty() )
				pushValue( "{}" );
			else
			{
				writeWithIndent( "{" );
				indentString_ += indentation_;
				VJson::Members::iterator it = members.begin();
				while ( true )
				{
					const std::string& name = *it;
					const VJson &childValue = value[name];
					writeWithIndent( valueToQuotedString( name.c_str() ) );
					*document_ << " : ";
					writeValue( childValue );
					if ( ++it == members.end() )
						break;
					*document_ << ",";
				}
				if( indentString_.size() < indentation_.size() )
				{
					errorf("VJsonStreamWriter in trouble!\n");
					return;
				}
				indentString_.resize( indentString_.size() - indentation_.size() );
				writeWithIndent( "}" );
			}
		}
		break;
	}
}

void VJsonStreamWriter::writeArrayValue( const VJson& value )
{
	uint size = value.size();
	if ( size == 0 )
		pushValue( "[]" );
	else
	{
		bool isArrayMultiLine = isMultineArray( value );
		if ( isArrayMultiLine )
		{
			writeWithIndent( "[" );
			indentString_ += indentation_;
			bool hasChildValue = !childValues_.empty();
			uint index =0;
			while ( true )
			{
				const VJson& childValue = value[index];
				if ( hasChildValue )
					writeWithIndent( childValues_[index] );
				else
				{
					writeIndent();
					writeValue( childValue );
				}
				if ( ++index == size )
					break;
				*document_ << ",";
			}
			if( indentString_.size() < indentation_.size() )
			{
				errorf("VJsonStreamWriter in trouble!\n");
				return;
			}
			indentString_.resize( indentString_.size() - indentation_.size() );
			writeWithIndent( "]" );
		}
		else // output on a single line
		{
			if( childValues_.size() != size )
			{
				errorf("VJsonStreamWriter in trouble!\n");
				return;
			}	
			*document_ << "[ ";
			for ( uint index =0; index < size; ++index )
			{
				if ( index > 0 )
					*document_ << ", ";
				*document_ << childValues_[index];
			}
			*document_ << " ]";
		}
	}
}

bool VJsonStreamWriter::isMultineArray( const VJson& value )
{
	int size = value.size();
	bool isMultiLine = size*3 >= rightMargin_ ;
	childValues_.clear();
	for ( int index =0; index < size && !isMultiLine; ++index )
	{
		const VJson& childValue = value[index];
		isMultiLine = (isMultiLine || ((childValue.isArray() || childValue.isObject()) && childValue.size() > 0));
	}
	if ( !isMultiLine ) // check if line length > max line length
	{
		childValues_.reserve( size );
		addChildValues_ = true;
		int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
		for ( int index =0; index < size && !isMultiLine; ++index )
		{
			writeValue( value[index] );
			lineLength += int( childValues_[index].length() );
		}
		addChildValues_ = false;
		isMultiLine = isMultiLine || lineLength >= rightMargin_;
	}
	return isMultiLine;
}

void VJsonStreamWriter::pushValue( const std::string& value )
{
	if ( addChildValues_ )
		childValues_.push_back( value );
	else
		*document_ << value;
}

void VJsonStreamWriter::writeIndent()
{
	/*
	Some comments in this method would have been nice.
	if ( !document_->empty() )
	{
		char last = document_[document_->length()-1];
		if ( last == ' ' )     // already indented
			return;
		if ( last != '\n' )    // Comments may add new-line
			*document_ << '\n';
	}
	*/
	*document_ << '\n' << indentString_;
}

void VJsonStreamWriter::writeWithIndent( const std::string& value )
{
	writeIndent();
	*document_ << value;
}


}

